package com.ysz.redisson.lock;

import com.ysz.redisson.config.RedisEncapsulation;
import com.ysz.redisson.task.ExecutorLua;
import com.ysz.redisson.threadpool.ThreadPool;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.internal.PlatformDependent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.Collections;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName RedissonLock
 * @Description redisson上锁
 * @Data 11:23
 * @Version 1.0
 * @Author ysz
 */
@Component
@Scope("prototype")
public class RedissonLock {

    @Autowired
    private ThreadPool threadPool;
    @Value("${redisson.timeOut}")
    private Long timeout;
    private final HashedWheelTimer timer = new HashedWheelTimer();
    private UUID id = UUID.randomUUID();
    private static final ConcurrentMap<String, ExpirationEntry> expirationRenewalMap = PlatformDependent.newConcurrentHashMap();
    private String name;


    public boolean tryLock(String lockName) {
        return tryLock(lockName, Thread.currentThread().getId());
    }


    public boolean tryLock(String name, long threadId) {
        if (StringUtils.isEmpty(name)) {
            throw new RuntimeException();
        }
        setEntryName(name);
        if (Objects.isNull(timeout)) {
            timeout = 30L;
        }
        long leaseTimeMillis = TimeUnit.MILLISECONDS.toMillis(timeout * 1000);
        Future<Long> submit = threadPool.getThreadPoolTaskExecutor()
                .submit(() -> ExecutorLua.executorLuaLock(Collections.singletonList(name), leaseTimeMillis, getLockName(threadId)));
        try {
            if (submit.get() == 1) {
                scheduleExpirationRenewal(leaseTimeMillis, name, threadId);
                return true;
            }
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public void unLock(String lockName) {
        RedisEncapsulation.deleteMap(lockName, getLockName(Thread.currentThread().getId()));
        cancelExpirationRenewal();
    }

    public void cancelExpirationRenewal() {
        ExpirationEntry expirationEntry = expirationRenewalMap.get(getEntryName());
        if (Objects.nonNull(expirationEntry)) {
            expirationEntry.getTimeout().cancel();
        }
    }

    private void scheduleExpirationRenewal(long leaseTimeMillis, String name, long threadId) {
        if (expirationRenewalMap.containsKey(getEntryName())) {
            return;
        }
        threadPool.getThreadPoolTaskExecutor().execute(() -> {
            Timeout task = timer.newTimeout(timeout -> {
                Long aLong = ExecutorLua.executorLuaRenew(Collections.singletonList(name), leaseTimeMillis, getLockName(threadId));
                expirationRenewalMap.remove(getEntryName());
                if (aLong != 1) {
                    timeout.cancel();
                } else {
                    scheduleExpirationRenewal(leaseTimeMillis, name, threadId);
                }
            }, leaseTimeMillis / 3, TimeUnit.MILLISECONDS);
            if (Objects.nonNull(expirationRenewalMap.putIfAbsent(getEntryName(), new ExpirationEntry(threadId, task)))) {
                expirationRenewalMap.get(name).getTimeout().cancel();
            }
        });
    }

    private String getLockName(long threadId) {
        return id + ":" + threadId;
    }

    private String getEntryName() {
        return name;
    }

    private void setEntryName(String name) {
        this.name = id + ":" + name;
    }
}
